﻿Module mMobAI

    Public Interface MobAI
        Sub Spawn(ByRef mob As MirMapMob)
        Sub DoTick(ByRef mob As MirMapMob, ByRef tc As Long)

    End Interface

    Public Function CanSee(mob As MirMapMob, plyr As MirMapPlayer, range As Integer) As Boolean

        If IsInsideArea(mob.Pos.INT2_X, mob.Pos.INT2_Y, plyr.conn.PositionInfo.INT2_X, plyr.conn.PositionInfo.INT2_Y, range) Then

            Return True

        Else

            Return False

        End If

    End Function

    Public Function GenRandDir() As Direction

        Dim wx As Integer
        Dim wy As Integer

        Do

            wx = Protocol.Functions.RandNum(-500, 500)
            wy = Protocol.Functions.RandNum(-500, 500)

            If wx < -300 Then
                wx = -1
            ElseIf wx > 300 Then
                wx = 1
            Else
                wx = 0
            End If

            If wy < -300 Then
                wy = -1
            ElseIf wy > 300 Then
                wy = 1
            Else
                wy = 0
            End If

        Loop Until Not (wy = 0 And wx = 0)

        Return GetDirFromOffsets(wx, wy)

    End Function

    Public Function GetDirFromOffsets(ByVal wx As Integer, ByVal wy As Integer) As Direction

        If wx = -1 Then
            Select Case wy
                Case -1
                    Return Direction.NW
                Case 0
                    Return Direction.W
                Case 1
                    Return Direction.SW
            End Select
        ElseIf wx = 1 Then
            Select Case wy
                Case -1
                    Return Direction.NE
                Case 0
                    Return Direction.E
                Case 1
                    Return Direction.SE
            End Select
        Else
            Select Case wy
                Case -1
                    Return Direction.N
                Case 0
                    Return Nothing
                Case 1
                    Return Direction.S
            End Select
        End If


    End Function

End Module

Friend Class MobAI1
    Implements MobAI

    Public Const AITickLength = 10000000

    Public Const AIChaseRange = 15
    Public Const AIAttackRange = 5

    Public Const SpawnEdgeRange = 1

    Public Sub DoTick(ByRef mob As mMobs.MirMapMob, ByRef tc As Long) Implements mMobAI.MobAI.DoTick

        If mob.AIState = 0 Then

            mob.AIMoveTime += 1

            Dim r As Integer = Int(Rnd() * 20)
            Dim c As Integer = mob.AIMoveTime
            Dim tried As Boolean

            If r <= c Then

                Dim tmp As cMobList.MobSpawn

                'r = Int(Rnd() * 250)
                'If r <= mob.stats.Speed And mob.stats.WalkStep Then

                'get spawn info
                tmp = mob.map.Mobs.GetSpawn(mob.ServerSpawnID)

retry:

                mob.Pos.BYTE_DIRECTION = GenRandDir()

                Dim p As Point = CalcNextStep(mob.Pos.INT2_X, mob.Pos.INT2_Y, mob.Pos.BYTE_DIRECTION)
                If Not mMaps.IsPassable(mob.map, p.X, p.Y) Then
                    If tried Then
                        With mob.stats
                            mob.AITimer = tc + .WalkSpeed
                        End With
                        Exit Sub
                    Else
                        tried = True
                        GoTo retry
                    End If
                End If

                'reset tried flag
                tried = False

retrygobacktospawn:

                If Not mob.map.Mobs.IsInRange(mob.ServerSpawnID, p.X, p.Y) Then
                    If tried Then
                        mob.AITimer = tc + mob.stats.WalkSpeed
                        Exit Sub
                    Else
                        tried = True
                        mob.Pos.BYTE_DIRECTION = mob.map.Mobs.GetSpawnDirection(mob)
                        'check if we can move in this direction
                        p = CalcNextStep(mob.Pos.INT2_X, mob.Pos.INT2_Y, mob.Pos.BYTE_DIRECTION)
                        If Not mMaps.IsPassable(mob.map, p.X, p.Y) Then
                            'way is blocked
                            If tried Then
                                'if we have aleady tried again once give up and try next tick
                                With mob.stats
                                    'increase aitimer to reduce server load
                                    mob.AITimer = tc + .WalkSpeed
                                End With
                                'give up
                                Exit Sub
                            Else
                                'remember this attempt
                                tried = True
                                '...and retry
                                GoTo retrygobacktospawn
                            End If
                        End If
                    End If

                End If

                mob.AIMoveTime = 0

                mob.Pos.BYTE_ANIM = MobAnimation.Walking

                MobUpdatePlayers(mob)

                mob.Pos.INT2_X = p.X
                mob.Pos.INT2_Y = p.Y

                mob.AIState = 1

                With mob.stats
                    mob.AITimer = tc + .WalkSpeed
                End With

            Else

                With mob.stats
                    mob.AITimer = tc + .WalkSpeed
                End With
                MobUpdatePlayers(mob)

            End If

        ElseIf mob.AIState = 1 Then

            mob.Pos.BYTE_ANIM = MobAnimation.Standing

            With mob.stats
                mob.AITimer = tc + .WalkSpeed
            End With
            MobUpdatePlayers(mob)

            mob.AIState = 0


        ElseIf mob.AIState = 2 Then

            Dim targetx As Integer, targety As Integer
            Dim victim As Connection = Nothing

            If mob.AITargetType = TargetType.Player Then

                SyncLock mob.map.objMapLock

                    With mob.map.Players

                        If .Count = 0 Then

                            mob.AIState = 0
                            Exit Sub

                        Else

                            Dim c As Integer = .Count - 1

                            For i As Integer = 0 To c

                                If .Item(i).conn.CharacterID = mob.AITargetIdx Then

                                    targetx = .Item(i).conn.PositionInfo.INT2_X
                                    targety = .Item(i).conn.PositionInfo.INT2_Y
                                    victim = .Item(i).conn

                                    If Not victim.IsAlive Then
                                        mob.AIState = 0
                                        mob.AIMoveTime = 20
                                        mob.AITimer = tc
                                        Exit Sub
                                    End If

                                End If

                            Next

                        End If

                    End With

                End SyncLock

            End If

            If (targetx = 0 And targety = 0) Then
                mob.AIState = 0
                Exit Sub
            End If

            targetx -= mob.Pos.INT2_X
            targety -= mob.Pos.INT2_Y


            If targetx > 20 Or targetx < -20 Or targety > 20 Or targety < -20 Then
                'target out of range
                mob.AIState = 0
                Exit Sub
            ElseIf targetx > -2 And targetx < 2 And targety > -2 And targety < 2 Then
                'hit

                If targetx < 0 Then targetx = -1
                If targetx > 0 Then targetx = 1
                If targety < 0 Then targety = -1
                If targety > 0 Then targety = 1

                mob.Pos.BYTE_ANIM = MobAnimation.MeleeAttack
                mob.Pos.BYTE_DIRECTION = GetDirFromOffsets(targetx, targety)

                With mob.stats
                    mob.AITimer = tc + .AttackSpeed
                End With

                MobUpdatePlayers(mob)

                mob.AIState = 3

                Exit Sub

            End If

            If targetx < 0 Then targetx = -1
            If targetx > 0 Then targetx = 1
            If targety < 0 Then targety = -1
            If targety > 0 Then targety = 1

            mob.Pos.BYTE_DIRECTION = GetDirFromOffsets(targetx, targety)
            Dim p As Point = CalcNextStep(mob.Pos.INT2_X, mob.Pos.INT2_Y, mob.Pos.BYTE_DIRECTION)
            If Not mMaps.IsPassable(mob.map, p.X, p.Y) Then
                Dim turndir As Integer
                turndir = Int(Rnd() * 2)
                If turndir = 0 Then turndir = -1
                For tries As Integer = 0 To 6

                    If turndir = -1 And mob.Pos.BYTE_DIRECTION = 0 Then
                        mob.Pos.BYTE_DIRECTION = 7
                    ElseIf turndir = 1 And mob.Pos.BYTE_DIRECTION = 7 Then
                        mob.Pos.BYTE_DIRECTION = 0
                    Else
                        mob.Pos.BYTE_DIRECTION += turndir
                    End If

                    p = CalcNextStep(mob.Pos.INT2_X, mob.Pos.INT2_Y, mob.Pos.BYTE_DIRECTION)
                    If mMaps.IsPassable(mob.map, p.X, p.Y) Then
                        Exit For
                    End If
                    If tries = 6 Then
                        mob.Pos.BYTE_ANIM = MobAnimation.Standing
                        With mob.stats
                            mob.AITimer = tc + .WalkSpeed
                        End With
                        MobUpdatePlayers(mob)
                        Exit Sub 'cant move
                    End If
                Next
            End If

            mob.Pos.BYTE_ANIM = MobAnimation.Walking

            With mob.stats
                mob.AITimer = tc + .WalkSpeed
            End With

            MobUpdatePlayers(mob)

            mob.Pos.INT2_X = p.X
            mob.Pos.INT2_Y = p.Y

        ElseIf mob.AIState = 3 Then

            Dim victim As Connection = Nothing

            If mob.AITargetType = TargetType.Player Then

                SyncLock mob.map.objMapLock
                    With mob.map.Players

                        If .Count = 0 Then

                            mob.AIState = 0
                            Exit Sub

                        Else

                            Dim c As Integer = .Count - 1

                            For i As Integer = 0 To c

                                If .Item(i).conn.CharacterID = mob.AITargetIdx Then

                                    victim = .Item(i).conn

                                End If

                            Next

                        End If

                    End With
                End SyncLock

            End If

            If victim Is Nothing Then
                mob.AIState = 0
                Exit Sub
            End If

            If Not victim.IsAlive Then
                mob.AIState = 0
                Exit Sub
            End If

            MobHitPlayer(mob, victim)

            'mob.AITimer += 4 * mob.Pos.INT8_SPEED
            mob.AIState = 2

        End If

    End Sub

    Public Sub Spawn(ByRef mob As mMobs.MirMapMob) Implements mMobAI.MobAI.Spawn

    End Sub

End Class

Friend Class MobAI2 : Implements MobAI

    Private Const c_AutoAttackRange As Integer = 5
    Private Const c_FollowRange As Integer = 15

    Public Sub DoTick(ByRef mob As MirMapMob, ByRef tc As Long) Implements MobAI.DoTick

        Select Case mob.AIState

            Case 0
                'targeting and movement
                DoAIState0(mob, tc)
            Case 1
                'standing
                DoAIState1(mob, tc)
            Case 2
                'follow/attack target
                DoAIState2(mob, tc)
            Case 3
                'calculate hits
                DoAIState3(mob, tc)

        End Select

    End Sub

    'handles hits
    Private Sub DoAIState3(ByRef mob As MirMapMob, ByRef tc As Long)

        Dim victim As Connection = Nothing
        'check for player target
        If mob.AITargetType = TargetType.Player Then

            SyncLock mob.map.objMapLock

                'find the player object
                With mob.map.Players

                    If .Count = 0 Then

                        'no players on this map, reset and give up
                        mob.AIState = 0
                        Exit Sub

                    Else

                        'itterate through players
                        Dim c As Integer = .Count - 1
                        For i As Integer = 0 To c

                            'check for matching IDs
                            If .Item(i).conn.CharacterID = mob.AITargetIdx Then

                                'found target, save a reference to its connection object
                                victim = .Item(i).conn
                                Exit For

                            End If

                        Next

                    End If

                End With

            End SyncLock

        End If

        'no target found
        If victim Is Nothing Then
            'reset and exit
            mob.AIState = 0
            Exit Sub
        End If

        'dead target
        If Not victim.IsAlive Then
            'reset and exit
            mob.AIState = 0
            Exit Sub
        End If

        'do hit calculations
        MobHitPlayer(mob, victim)

        'go back to targeting and attacking
        mob.AIState = 2

    End Sub

    'handles targeting and attacking
    Private Sub DoAIState2(ByRef mob As MirMapMob, ByRef tc As Long)

        Dim targetx As Integer, targety As Integer
        Dim victim As Connection = Nothing


        If mob.AITargetType = TargetType.Player Then
            'target type is a player

            SyncLock mob.map.objMapLock

                With mob.map.Players

                    'if map has no players reset to state 0 and exit
                    If .Count = 0 Then

                        mob.AIState = 0
                        Exit Sub

                    Else

                        'itterate through players on this map
                        Dim c As Integer = .Count - 1
                        For i As Integer = 0 To c
                            'check for matching target ID
                            If .Item(i).conn.CharacterID = mob.AITargetIdx Then

                                'set target vars
                                targetx = .Item(i).conn.PositionInfo.INT2_X
                                targety = .Item(i).conn.PositionInfo.INT2_Y
                                victim = .Item(i).conn

                                'check for a dead target
                                If Not victim.IsAlive Then
                                    'reset and force immediate movement
                                    mob.AIState = 0
                                    mob.AIMoveTime = 20
                                    mob.AITimer = tc
                                    'exit
                                    Exit Sub
                                End If

                                Exit For

                            End If
                        Next

                    End If

                End With

            End SyncLock

        End If

        'check for invalid target
        If (targetx = 0 And targety = 0) Then
            'reset state and exit as target is invalid
            mob.AIState = 0
            Exit Sub
        End If

        'work out target offset from mob
        targetx -= mob.Pos.INT2_X
        targety -= mob.Pos.INT2_Y


        'check range
        If targetx > c_FollowRange Or targetx < (-1 * c_FollowRange) Or targety > c_FollowRange Or targety < (-1 * c_FollowRange) Then
            'target out of range
            mob.AIState = 0
            Exit Sub


            'check if we are next to the target
        ElseIf targetx > -2 And targetx < 2 And targety > -2 And targety < 2 Then
            'hit target

            If targetx < 0 Then targetx = -1
            If targetx > 0 Then targetx = 1
            If targety < 0 Then targety = -1
            If targety > 0 Then targety = 1

            'set anim to attack
            mob.Pos.BYTE_ANIM = MobAnimation.MeleeAttack
            'set direction to point towards target
            mob.Pos.BYTE_DIRECTION = GetDirFromOffsets(targetx, targety)

            'set aitimer for next tick
            With mob.stats
                mob.AITimer = tc + .AttackSpeed
            End With

            'update players nearby
            MobUpdatePlayers(mob)

            'set aistate for next stage
            mob.AIState = 3

            'exit
            Exit Sub

        End If

        'calculate best direction to travel towards target
        If targetx < 0 Then targetx = -1
        If targetx > 0 Then targetx = 1
        If targety < 0 Then targety = -1
        If targety > 0 Then targety = 1
        mob.Pos.BYTE_DIRECTION = GetDirFromOffsets(targetx, targety)

        'check if this direction is free to movbe in
        Dim p As Point = CalcNextStep(mob.Pos.INT2_X, mob.Pos.INT2_Y, mob.Pos.BYTE_DIRECTION)
        If Not mMaps.IsPassable(mob.map, p.X, p.Y) Then
            'way is blocked
            'turn randomly to avoid tyhe obstacle
            Dim turndir As Integer
            turndir = Int(Rnd() * 2)
            If turndir = 0 Then turndir = -1

            'make 6 attempts to turn a different direction before giving up
            For tries As Integer = 0 To 6

                'alter direction in the chosen fashing
                If turndir = -1 And mob.Pos.BYTE_DIRECTION = 0 Then
                    mob.Pos.BYTE_DIRECTION = 7
                ElseIf turndir = 1 And mob.Pos.BYTE_DIRECTION = 7 Then
                    mob.Pos.BYTE_DIRECTION = 0
                Else
                    mob.Pos.BYTE_DIRECTION += turndir
                End If

                'check if we can move forward
                p = CalcNextStep(mob.Pos.INT2_X, mob.Pos.INT2_Y, mob.Pos.BYTE_DIRECTION)
                If mMaps.IsPassable(mob.map, p.X, p.Y) Then
                    'success exit the for loop
                    Exit For
                End If

                If tries = 6 Then
                    'max tries exhausted
                    'stand
                    mob.Pos.BYTE_ANIM = MobAnimation.Standing
                    With mob.stats
                        'increment the aitimer
                        mob.AITimer = tc + .WalkSpeed
                    End With
                    'update nearby players
                    MobUpdatePlayers(mob)
                    'exit
                    Exit Sub
                End If
            Next
        End If

        'no need to alter ai state as we have a target

        'move
        mob.Pos.BYTE_ANIM = MobAnimation.Walking
        'increment ai timer
        With mob.stats
            mob.AITimer = tc + .WalkSpeed
        End With
        'update local players
        MobUpdatePlayers(mob)

        'update position
        mob.Pos.INT2_X = p.X
        mob.Pos.INT2_Y = p.Y

    End Sub

    'stands after movement
    Private Sub DoAIState1(ByRef mob As MirMapMob, ByRef tc As Long)

        'make mob stand still
        mob.Pos.BYTE_ANIM = MobAnimation.Standing
        MobUpdatePlayers(mob)

        'increase aitimer
        With mob.stats
            mob.AITimer = tc + .WalkSpeed
        End With

        'go back to looking for targets and walking randomly
        mob.AIState = 0

    End Sub

    'searches for targets and moves randomly if it cant find one
    Private Sub DoAIState0(ByRef mob As MirMapMob, ByRef tc As Long)

        'iterate through players on same map

        SyncLock mob.map.objMapLock

            For Each plyr As MirMapPlayer In mob.map.Players

                'check if this player is withing auto attack range
                If CanSee(mob, plyr, c_AutoAttackRange) Then

                    'target this player
                    mob.AITargetType = mMobs.TargetType.Player
                    mob.AITargetIdx = plyr.conn.CharacterID

                    mob.AIState = 2
                    Exit Sub
                End If

            Next

        End SyncLock

        'no targets to attack, move instead

        'increase movement timer by 1
        mob.AIMoveTime += 1

        Dim tried As Boolean

        'check random number against movetimer, must be less to move
        If r <= mob.AIMoveTime Then

            Dim tmp As cMobList.MobSpawn

            'get spawn info
            tmp = mob.map.Mobs.GetSpawn(mob.ServerSpawnID)

retry:

            mob.Pos.BYTE_DIRECTION = GenRandDir()

            'check if we can move in this direction
            Dim p As Point = CalcNextStep(mob.Pos.INT2_X, mob.Pos.INT2_Y, mob.Pos.BYTE_DIRECTION)
            If Not mMaps.IsPassable(mob.map, p.X, p.Y) Then
                'way is blocked
                If tried Then
                    'if we have aleady tried again once give up and try next tick
                    With mob.stats
                        'increase aitimer to reduce server load
                        mob.AITimer = tc + .WalkSpeed
                    End With
                    'give up
                    Exit Sub
                Else
                    'remember this attempt
                    tried = True
                    '...and retry
                    GoTo retry
                End If
            End If

            'reset tried flag
            tried = False

retrygobacktospawn:

            If Not mob.map.Mobs.IsInRange(mob.ServerSpawnID, p.X, p.Y) Then
                If tried Then
                    mob.AITimer = tc + mob.stats.WalkSpeed
                    Exit Sub
                Else
                    tried = True
                    mob.Pos.BYTE_DIRECTION = mob.map.Mobs.GetSpawnDirection(mob)
                    'check if we can move in this direction
                    p = CalcNextStep(mob.Pos.INT2_X, mob.Pos.INT2_Y, mob.Pos.BYTE_DIRECTION)
                    If Not mMaps.IsPassable(mob.map, p.X, p.Y) Then
                        'way is blocked
                        If tried Then
                            'if we have aleady tried again once give up and try next tick
                            With mob.stats
                                'increase aitimer to reduce server load
                                mob.AITimer = tc + .WalkSpeed
                            End With
                            'give up
                            Exit Sub
                        Else
                            'remember this attempt
                            tried = True
                            '...and retry
                            GoTo retrygobacktospawn
                        End If
                    End If
                End If

            End If


            'check if this movement is withing this mobs spawn area
            'If Not mob.map.Mobs.IsInRange(mob.ServerSpawnID, p.X, p.Y) Then
            '    'this move will take us outside our range
            '    If tried Then
            '        'we have tried already, give up and try again later
            '        With mob.stats
            '            mob.AITimer = tc + .WalkSpeed
            '        End With
            '        Exit Sub
            '    Else
            '        'remember this attempt and retry
            '        tried = True
            '        GoTo retry
            '    End If
            'End If

            'reset movement timer as we are about to take a step
            mob.AIMoveTime = 0

            'move the mob
            mob.Pos.BYTE_ANIM = MobAnimation.Walking

            MobUpdatePlayers(mob)

            'set new position
            mob.Pos.INT2_X = p.X
            mob.Pos.INT2_Y = p.Y

            'set aistate to stand next tick
            mob.AIState = 1

            'increase aitimer
            With mob.stats
                mob.AITimer = tc + .WalkSpeed
            End With

        Else

            'generated random number did not tell us to move try later
            With mob.stats
                mob.AITimer = tc + .WalkSpeed
            End With
            MobUpdatePlayers(mob)

        End If

    End Sub

    Public Sub Spawn(ByRef mob As MirMapMob) Implements MobAI.Spawn

    End Sub
End Class

